package com.q3d.demo.common.lib.service.exception;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import com.q3d.demo.common.lib.auth.UnAuthorizedException;
import com.q3d.demo.common.lib.service.response.ResponseBase;
import com.q3d.demo.common.lib.service.response.ResponseStatus;

import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

@RestControllerAdvice /** RestControllerAdvice = ControllerAdvice + ResponseBody */
/**
 * 统一异常处理 Advice
 * 
 * @author 叶湘 ( weixin:yexiang841, email:yexiang841@qq.com )
 */
public class ServiceExceptionAdvice {

        /**
         * 缺少参数异常
         *
         * @param e A MissingServletRequestParameterException
         * @return ResponseBase
         */
        @ExceptionHandler(value = MissingServletRequestParameterException.class)
        public ResponseBase<Object> handleError(MissingServletRequestParameterException e) {
                String message = String.format("%s - %s", ResponseStatus.BAD_REQUEST_PARAM_MISS.getAnswer(),
                                e.getParameterName());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_PARAM_MISS);
        }

        /**
         * 参数类型错误异常
         *
         * @param e A MethodArgumentTypeMismatchException
         * @return ResponseBase
         */
        @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
        public ResponseBase<Object> handleError(MethodArgumentTypeMismatchException e) {
                String message = String.format("%s - %s", ResponseStatus.BAD_REQUEST_PARAM_TYPE_ERROR.getAnswer(),
                                e.getName());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_PARAM_TYPE_ERROR);
        }

        /**
         * 参数值异常(比如参数长度超限)
         *
         * @param e A MethodArgumentNotValidException
         * @return ResponseBase
         */
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ResponseBase<Object> handleError(MethodArgumentNotValidException e) {
                BindingResult result = e.getBindingResult();
                FieldError error = result.getFieldError();
                String message = String.format("%s - %s - %s",
                                ResponseStatus.BAD_REQUEST_PARAM_VALID_ERROR.getAnswer(),
                                error.getField(), error.getDefaultMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_PARAM_VALID_ERROR);
        }

        /**
         * 参数转换异常(比如自动类型转换失败)
         *
         * @param e A BindException
         * @return ResponseBase
         */
        @ExceptionHandler(value = BindException.class)
        public ResponseBase<Object> handleError(BindException e) {
                FieldError error = e.getFieldError();
                String message = String.format("%s - %s - %s", ResponseStatus.BAD_REQUEST_PARAM_BIND_ERROR.getAnswer(),
                                error.getField(), error.getDefaultMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_PARAM_BIND_ERROR);
        }

        /**
         * 空页面(404)异常
         *
         * @param e A NoHandlerFoundException
         * @return ResponseBase
         */
        @ExceptionHandler(value = NoHandlerFoundException.class)
        public ResponseBase<Object> handleError(NoHandlerFoundException e) {
                String message = String.format("%s - %s", ResponseStatus.NOT_FOUND.getAnswer(),
                                e.getCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.NOT_FOUND);
        }

        /**
         * Http 请求消息序列化异常
         *
         * @param e A HttpMessageNotReadableException
         * @return ResponseBase
         */
        @ExceptionHandler(value = HttpMessageNotReadableException.class)
        public ResponseBase<?> handleError(HttpMessageNotReadableException e) {
                String message = String.format("%s - %s", ResponseStatus.BAD_REQUEST_MSG_NOT_READABLE.getAnswer(),
                                e.getRootCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_MSG_NOT_READABLE);
        }

        /**
         * 请求方式错误异常(比如用 Get 请求 PostMapping 接口)
         *
         * @param e A HttpRequestMethodNotSupportedException
         * @return ResponseBase
         */
        @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
        public ResponseBase<?> handleError(HttpRequestMethodNotSupportedException e) {
                String message = String.format("%s - %s", ResponseStatus.METHOD_NOT_ALLOWED.getAnswer(),
                                e.getCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.METHOD_NOT_ALLOWED);
        }

        /**
         * 请求结构体类型错误异常(常见于 RequestBody 类型为 String / JSON 的问题)
         *
         * @param e A HttpMediaTypeNotSupportedException
         * @return ResponseBase
         */
        @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
        public ResponseBase<?> handleError(HttpMediaTypeNotSupportedException e) {
                String message = String.format("%s - %s", ResponseStatus.UNSUPPORTED_MEDIA_TYPE.getAnswer(),
                                e.getCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.UNSUPPORTED_MEDIA_TYPE);
        }

        /**
         * 自定义的未授权异常
         *
         * @param e A UnAuthorizedException
         * @return ResponseBase
         */
        @ExceptionHandler(value = UnAuthorizedException.class)
        public ResponseBase<?> handleError(UnAuthorizedException e) {
                String message = String.format("%s - %s", e.getResponseStatus().getAnswer(), e.getCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(e.getResponseStatus());
        }

        /**
         * JPA 数据校验异常
         *
         * @param e A ConstraintViolationException
         * @return ResponseBase
         */
        @ExceptionHandler(value = ConstraintViolationException.class)
        public ResponseBase<?> handleError(ConstraintViolationException e) {
                Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
                ConstraintViolation<?> violation = violations.iterator().next();
                String message = String.format("%s - %s", ResponseStatus.BAD_REQUEST_PARAM_VALID_ERROR.getAnswer(),
                                violation.getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(ResponseStatus.BAD_REQUEST_PARAM_VALID_ERROR);
        }

        /**
         * 自定义的 ServiceException 异常
         *
         * @param e A ServiceException
         * @return ResponseBase
         */
        @ExceptionHandler(value = ServiceException.class)
        public ResponseBase<?> handleError(ServiceException e) {
                String message = String.format("%s - %s", e.getResponseStatus().getAnswer(), e.getCause().getMessage());
                return ResponseBase
                                .builder()
                                .message(message)
                                .build()
                                .status(e.getResponseStatus());
        }

        /**
         * 未在上述列举之中的未知异常（包括空指针、除零异常都在这里捕获）
         * 这些异常不会对外暴露异常信息，统一返回500，详细记录在日志文件
         *
         * @param e A Throwable
         * @return ResponseBase
         */
        @ExceptionHandler(value = Throwable.class)
        public ResponseBase<?> handleError(Throwable e) {
                /** 未知异常不要对外暴露具体异常信息 */
                return ResponseBase
                                .builder()
                                .message(ResponseStatus.INTERNAL_SERVER_ERROR.getAnswer())
                                .build()
                                .status(ResponseStatus.INTERNAL_SERVER_ERROR);
        }

}
